001 /* 002 * Copyright 2004-2005 Stephen J. McConnell. 003 * Copyright 2004 Niclas Hedhman. 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 014 * implied. 015 * 016 * See the License for the specific language governing permissions and 017 * limitations under the License. 018 */ 019 020 package net.dpml.transit; 021 022 import java.io.Serializable; 023 import java.net.URLStreamHandler; 024 025 import java.net.MalformedURLException; 026 import java.net.URI; 027 import java.net.URISyntaxException; 028 import java.net.URL; 029 030 /** 031 * A utility class the handles validation of <code>artifact</code> style uri 032 * strings. 033 * 034 * @author <a href="http://www.dpml.net">Digital Product Meta Library</a> 035 * @version 1.0.1 036 */ 037 public final class Artifact implements Serializable, Comparable 038 { 039 /** 040 * Constant scheme name for the artifact protocol. 041 */ 042 public static final String ARTIFACT = "artifact"; 043 044 /** 045 * Constant scheme name for the link protocol. 046 */ 047 public static final String LINK = "link"; 048 049 /** 050 * Constant scheme name for the local protocol. 051 */ 052 public static final String LOCAL = "local"; 053 054 static final long serialVersionUID = 1L; 055 056 // ------------------------------------------------------------------------ 057 // static 058 // ------------------------------------------------------------------------ 059 060 /** 061 * Creation of a new artifact instance using a supplied uri specification. 062 * An artifact uri contains the protocol identifier, a type, a group 063 * designator, a name, and an optional version identifier. 064 * <p>The following represent valid artifact uri examples:</p> 065 * 066 * <ul> 067 * <li>artifact:jar:dpml/transit/dpml-transit-main#1234</li> 068 * <li>artifact:jar:dpml/transit/dpml-transit-main</li> 069 * <li>link:jar:dpml/transit/dpml-transit-main#1.0</li> 070 * </ul> 071 * 072 * <p> 073 * If there is a internal reference identifier which is marked by the 074 * exclamation mark followed by slash (!/) it will be stripped. The 075 * version part can be either before or after such identifier. Example; 076 * <pre> 077 * artifact:war:jmx-html/jmx-html#1.3!/images/abc.png 078 * artifact:war:jmx-html/jmx-html!/images/abc.png#1.3 079 * </pre> 080 * The above uris will both be referencing 081 * <code>artifact:war:jmx-html/jmx-html#1.3</code> 082 * </p> 083 * @param uri the artifact uri 084 * @return the new artifact 085 * @exception java.net.URISyntaxException if the supplied uri is not valid. 086 * @exception UnsupportedSchemeException if the URI does not have "artifact" 087 * or "link" as its <strong>scheme</strong>. 088 */ 089 public static final Artifact createArtifact( String uri ) 090 throws URISyntaxException, UnsupportedSchemeException 091 { 092 if( null == uri ) 093 { 094 throw new NullArgumentException( "uri" ); 095 } 096 int asterix = uri.indexOf( "!" ); 097 if( asterix == -1 ) 098 { 099 return createArtifact( new URI( uri ) ); 100 } 101 else 102 { 103 String path = uri.substring( 0, asterix ); 104 int versionPos = uri.indexOf( "#" ); 105 if( versionPos < asterix ) 106 { 107 return createArtifact( path ); 108 } 109 else 110 { 111 path = path + uri.substring( versionPos ); 112 return createArtifact( path ); 113 } 114 } 115 } 116 117 /** 118 * Creation of a new artifact instance using a supplied uri specification. An 119 * artifact uri contains the protocol identifier, an optional type, a group 120 * designator, a name, and an optional version identifier. 121 * <p>The following represent valid artifact uri examples:</p> 122 * 123 * <ul> 124 * <li>artifact:jar:metro/cache/dpml-cache-main#1.0.0</li> 125 * <li>artifact:metro/cache/dpml-cache-main#1.0.0</li> 126 * <li>artifact:metro/cache/dpml-cache-main</li> 127 * </ul> 128 * 129 * @param uri the artifact uri 130 * @return the new artifact 131 * @exception UnsupportedSchemeException if the URI does not have "artifact" 132 * or "link" as its <strong>scheme</strong>. 133 */ 134 public static final Artifact createArtifact( URI uri ) 135 throws UnsupportedSchemeException 136 { 137 if( null == uri ) 138 { 139 throw new NullArgumentException( "uri" ); 140 } 141 String scheme = uri.getScheme(); 142 if( null == scheme ) 143 { 144 final String error = 145 "URI does not declare a scheme: " + uri; 146 throw new UnsupportedSchemeException( error ); 147 } 148 //if( !scheme.equals( ARTIFACT ) && !scheme.equals( LINK ) && !scheme.equals( LOCAL ) ) 149 //{ 150 // final String error = 151 // "URI contains a scheme that is not recognized: " + uri; 152 // throw new UnsupportedSchemeException( error ); 153 //} 154 return new Artifact( uri ); 155 } 156 157 /** 158 * Creation of a new artifact instance using a supplied group, name, 159 * version and type arguments. 160 * 161 * @param group the artifact group identifier 162 * @param name the artifact name 163 * @param version the version 164 * @param type the type 165 * @return the new artifact 166 * @exception NullArgumentException if any of the <code>group</code>, 167 * <code>name</code> or <code>type</code> arguments are 168 * <code>null</code>. 169 */ 170 public static Artifact createArtifact( String group, String name, String version, String type ) 171 throws NullArgumentException 172 { 173 return createArtifact( ARTIFACT, group, name, version, type ); 174 } 175 176 /** 177 * Creation of a new artifact instance using a supplied group, name, 178 * version and type arguments. 179 * 180 * @param scheme the artifact scheme 181 * @param group the artifact group identifier 182 * @param name the artifact name 183 * @param version the version 184 * @param type the type 185 * @return the new artifact 186 * @exception NullArgumentException if any of the <code>group</code>, 187 * <code>name</code> or <code>type</code> arguments are 188 * <code>null</code>. 189 */ 190 public static Artifact createArtifact( String scheme, String group, String name, String version, String type ) 191 throws NullArgumentException 192 { 193 if( name == null ) 194 { 195 throw new NullArgumentException( "name" ); 196 } 197 if( type == null ) 198 { 199 throw new NullArgumentException( "type" ); 200 } 201 if( scheme == null ) 202 { 203 throw new NullArgumentException( "scheme" ); 204 } 205 String composite = buildComposite( scheme, group, name, version, type ); 206 try 207 { 208 URI uri = new URI( composite ); 209 return new Artifact( uri ); 210 } 211 catch( URISyntaxException e ) 212 { 213 // Can not happen. 214 final String error = 215 "An internal error has occurred. " 216 + "The following URI could not be constructed: " + composite; 217 throw new TransitRuntimeException( error ); 218 } 219 } 220 221 private static String buildComposite( String scheme, String group, String name, String version, String type ) 222 { 223 if( null == group ) 224 { 225 if( null == version ) 226 { 227 return scheme + ":" + type + ":" + name; 228 } 229 else 230 { 231 return scheme + ":" + type + ":" + name + "#" + version; 232 } 233 } 234 else 235 { 236 if( null == version ) 237 { 238 return scheme + ":" + type + ":" + group + "/" + name; 239 } 240 else 241 { 242 return scheme + ":" + type + ":" + group + "/" + name + "#" + version; 243 } 244 } 245 } 246 247 /** 248 * Construct a new URL form a given URI. If the URI is a Transit URI the 249 * returned URL will be associated with the appropriate handler. 250 * @param uri the uri to convert 251 * @return the converted url 252 * @exception MalformedURLException if the url could not be created 253 */ 254 public static URL toURL( URI uri ) throws MalformedURLException 255 { 256 try 257 { 258 Artifact artifact = Artifact.createArtifact( uri ); 259 return artifact.toURL(); 260 } 261 catch( UnsupportedSchemeException e ) 262 { 263 } 264 catch( IllegalArgumentException e ) 265 { 266 } 267 268 try 269 { 270 return uri.toURL(); 271 } 272 catch( MalformedURLException mue ) 273 { 274 throw mue; 275 } 276 catch( Throwable t ) 277 { 278 final String error = 279 "Unexpected error while attempting to convert a uri to a url." 280 + "\n URI: " 281 + uri; 282 throw new TransitRuntimeException( error, t ); 283 } 284 } 285 286 /** 287 * Test if the supplied uri is from the artifact family. Specificially 288 * the test validates that the supplied uri has a scheme corresponding to 289 * 'artifact', link', or 'local'. 290 * @param uri the uri to check 291 * @return true if thie uri is artifact based 292 */ 293 public static boolean isRecognized( URI uri ) 294 { 295 String scheme = uri.getScheme(); 296 if( ARTIFACT.equals( scheme ) ) 297 { 298 return true; 299 } 300 else if( LINK.equals( scheme ) ) 301 { 302 return true; 303 } 304 else 305 { 306 return LOCAL.equals( scheme ); 307 } 308 } 309 310 // ------------------------------------------------------------------------ 311 // state 312 // ------------------------------------------------------------------------ 313 314 /** 315 * The artifact uri. 316 */ 317 private final URI m_uri; 318 319 /** 320 * The artifact group. 321 */ 322 private final String m_group; 323 324 /** 325 * The artifact name. 326 */ 327 private final String m_name; 328 329 /** 330 * The artifact type. 331 */ 332 private final String m_type; 333 334 // ------------------------------------------------------------------------ 335 // constructor 336 // ------------------------------------------------------------------------ 337 338 /** 339 * Creation of a new Artifact using a supplied uri. 340 * @param uri a uri of the form [scheme]:[type]:[group]/[name]#[version] 341 * where [scheme] is one of 'link', 'artifact' or 'local'. 342 */ 343 private Artifact( URI uri ) 344 throws IllegalArgumentException 345 { 346 m_uri = uri; 347 String ssp = uri.getSchemeSpecificPart(); 348 349 if( ssp.indexOf( "//" ) > -1 350 || ssp.indexOf( ":/" ) > -1 351 || ssp.endsWith( "/" ) ) 352 { 353 final String error = 354 "Invalid character sequence in uri [" 355 + uri + "]."; 356 throw new IllegalArgumentException( error ); 357 } 358 359 int typeIndex = ssp.indexOf( ':' ); 360 if( typeIndex > -1 ) 361 { 362 String type = ssp.substring( 0, typeIndex ); 363 m_type = type; 364 ssp = ssp.substring( typeIndex + 1 ); 365 } 366 else 367 { 368 final String error = "Supplied artifact specification [" 369 + uri + "] does not contain a type."; 370 throw new IllegalArgumentException( error ); 371 } 372 373 // ssp now contains group, name and version 374 375 int groupIndex = ssp.lastIndexOf( '/' ); 376 if( groupIndex > -1 ) 377 { 378 String group = ssp.substring( 0, groupIndex ); 379 m_group = group; 380 m_name = ssp.substring( groupIndex + 1 ); 381 } 382 else 383 { 384 m_group = null; 385 m_name = ssp; 386 } 387 388 String ver = uri.getFragment(); 389 if( ver != null ) 390 { 391 if( ver.indexOf( '/' ) >= 0 392 || ver.indexOf( '%' ) >= 0 393 || ver.indexOf( '\\' ) >= 0 394 || ver.indexOf( '*' ) >= 0 395 || ver.indexOf( '!' ) >= 0 396 || ver.indexOf( '(' ) >= 0 397 || ver.indexOf( '@' ) >= 0 398 || ver.indexOf( ')' ) >= 0 399 || ver.indexOf( '+' ) >= 0 400 || ver.indexOf( '\'' ) >= 0 401 || ver.indexOf( '{' ) >= 0 402 || ver.indexOf( '}' ) >= 0 403 || ver.indexOf( '[' ) >= 0 404 || ver.indexOf( '}' ) >= 0 405 || ver.indexOf( '?' ) >= 0 406 || ver.indexOf( ',' ) >= 0 407 || ver.indexOf( '#' ) >= 0 408 || ver.indexOf( '=' ) >= 0 409 ) 410 { 411 final String error = 412 "Supplied artifact specification [" 413 + uri 414 + "] contains illegal characters in the Version part."; 415 throw new IllegalArgumentException( error ); 416 } 417 } 418 } 419 420 // ------------------------------------------------------------------------ 421 // public 422 // ------------------------------------------------------------------------ 423 424 /** 425 * Return the protocol for the artifact. 426 * 427 * @return the protocol scheme 428 */ 429 public final String getScheme() 430 { 431 return m_uri.getScheme(); 432 } 433 434 /** 435 * Return the group identifier for the artifact. The group identifier 436 * is composed of a sequence of named separated by the '/' character. 437 * 438 * @return the group identifier 439 */ 440 public final String getGroup() 441 { 442 return m_group; 443 } 444 445 /** 446 * Return the name of the artifact. 447 * 448 * @return the artifact name 449 */ 450 public final String getName() 451 { 452 return m_name; 453 } 454 455 /** 456 * Return the type of the artifact. 457 * 458 * @return the artifact type 459 */ 460 public final String getType() 461 { 462 return m_type; 463 } 464 465 /** 466 * Return the posssibly null version identifier. The value of the version 467 * is an opaque string. 468 * @return the artifact version 469 */ 470 public final String getVersion() 471 { 472 String ver = m_uri.getFragment(); 473 if( ver == null ) 474 { 475 return null; 476 } 477 else if( ver.length() == 0 ) 478 { 479 return null; 480 } 481 else 482 { 483 return ver; 484 } 485 } 486 487 /** 488 * Test if the artifact scheme is recognized. Specificially 489 * the test validates that the artifact scheme corresponding to 490 * 'artifact', link', or 'local'. 491 * @return true if the uri scheme is recognized 492 */ 493 public boolean isRecognized() 494 { 495 return isRecognized( m_uri ); 496 } 497 498 /** 499 * Create an artifact url backed by the repository. 500 * 501 * @return the artifact url 502 */ 503 public URL toURL() 504 { 505 String scheme = getScheme(); 506 if( ARTIFACT.equals( scheme ) ) 507 { 508 return toURL( new net.dpml.transit.artifact.Handler() ); 509 } 510 else if( LINK.equals( scheme ) ) 511 { 512 return toURL( new net.dpml.transit.link.Handler() ); 513 } 514 else if( LOCAL.equals( scheme ) ) 515 { 516 return toURL( new net.dpml.transit.local.Handler() ); 517 } 518 else 519 { 520 final String error = 521 "URI scheme not recognized: " + m_uri; 522 throw new UnsupportedSchemeException( error ); 523 } 524 } 525 526 /** 527 * Create an artifact url backed by the repository. 528 * @param handler the protocol handler 529 * @return the artifact url 530 */ 531 public URL toURL( URLStreamHandler handler ) 532 { 533 try 534 { 535 return new URL( null, m_uri.toASCIIString(), handler ); 536 } 537 catch( MalformedURLException e ) 538 { 539 // Can not happen! 540 final String error = 541 "An artifact URI could not be converted to a URL [" 542 + m_uri 543 + "]."; 544 throw new TransitRuntimeException( error ); 545 } 546 } 547 548 /** 549 * Create an artifact url backed by the repository. 550 * 551 * @return the artifact url 552 */ 553 public URI toURI() 554 { 555 return m_uri; 556 } 557 558 // ------------------------------------------------------------------------ 559 // Comparable 560 // ------------------------------------------------------------------------ 561 562 /** 563 * Compare this artifact with another artifact. Artifact comparisom is 564 * based on a comparison of the string representation of the artifact with 565 * the string representation of the supplied object. 566 * 567 * @param object the object to compare with this instance 568 * @return the comparative order of the supplied object relative to this 569 * artifact 570 * @exception NullArgumentException if the supplied object argument is null. 571 * @exception ClassCastException if the supplied object is not an Artifact. 572 */ 573 public int compareTo( Object object ) 574 throws NullArgumentException, ClassCastException 575 { 576 if( object instanceof Artifact ) 577 { 578 String name = this.toString(); 579 return name.compareTo( object.toString() ); 580 } 581 else if( null == object ) 582 { 583 throw new NullArgumentException( "object" ); 584 } 585 else 586 { 587 final String error = 588 "Object [" 589 + object.getClass().getName() 590 + "] does not implement [" 591 + this.getClass().getName() + "]."; 592 throw new ClassCastException( error ); 593 } 594 } 595 596 // ------------------------------------------------------------------------ 597 // Object 598 // ------------------------------------------------------------------------ 599 600 /** 601 * Return a string representation of the artifact. 602 * @return the artifact as a uri 603 */ 604 public String toString() 605 { 606 return m_uri.toString(); 607 } 608 609 /** 610 * Compare this artifact with the supplied object for equality. This method 611 * will return true if the supplied object is an Artifact and has an equal 612 * uri. 613 * 614 * @param other the object to compare with this instance 615 * @return TRUE if this artifact is equal to the supplied object 616 */ 617 public boolean equals( Object other ) 618 { 619 if( null == other ) 620 { 621 return false; 622 } 623 else if( this == other ) 624 { 625 return true; 626 } 627 else if( other instanceof Artifact ) 628 { 629 Artifact art = (Artifact) other; 630 return m_uri.equals( art.m_uri ); 631 } 632 else 633 { 634 return false; 635 } 636 } 637 638 /** 639 * Return the hashcode for the artifact. 640 * @return the hashcode value 641 */ 642 public int hashCode() 643 { 644 return m_uri.hashCode(); 645 } 646 } 647